// C source file with the function definitions to handle bits

// clear_bit_holder() function
// function to make the contents of the bit holder array to be 0
void clear_bit_holder(void)
{
  for (umax i = 0; i < BIT_HOLDER_LENGTH; i++)
    BIT_HOLDER[i] = 0;
}


// clear_byte_holder() function
// function to make the contents of the byte holder array to be 0
void clear_byte_holder(void)
{
  for (umax i = 0; i < BYTE_HOLDER_LENGTH; i++)
    BYTE_HOLDER[i] = 0;
}


// print_bit_holder() function
// function print the contents of the bit holder array
void print_bit_holder(void)
{
  for (umax i = 0; i < BIT_HOLDER_LENGTH; i++)
    printf("%u ", BIT_HOLDER[i]);
  printf("\n");
}


// print_byte_holder() function
// function print the contents of the byte holder array
void print_byte_holder(void)
{
  for (umax i = 0; i < BYTE_HOLDER_LENGTH; i++)
    printf("%02X ", BYTE_HOLDER[i]);
  printf("\n");
}


// arr_bitshift() function
// bitshift the bits of an array by 1 either at the
// right (BITSHIFT_RIGHT) or at the left (BITSHIFT_LEFT)
// bits are moved from a variable to the other when it is needed
byte arr_bitshift(void * array, umax arr_size, umax var_size, BITSHIFT_T bitshift_type)
{
  // initialize variables
  umax byte_cnt = arr_size * var_size; // number of bytes on the array
  byte * cur_byte = array; // casting the array pointer
  // containers for bits that are meant to be passed to another byte variable
  byte new_bit = 0, old_bit = 0;

  // check which type of shift to perform (right/left)
  // this process depends on endianness as well (fucking hell)
  
  // left bitshift
  // go throught the array from right to left
  if (bitshift_type == BITSHIFT_LEFT)
  {
    if (SYS_ENDIAN == LITTLE)
    {
      // get to the end of the first variable
      cur_byte += byte_cnt - var_size;
      for (umax i = 0; i < arr_size; i++)
      {
        for (umax j = 0; j < var_size; j++)
        {
          new_bit = (* cur_byte) >> (CHAR_BIT - 1);
          (* cur_byte) = (* cur_byte) << 1;
          (* cur_byte) += old_bit;
          old_bit = new_bit;
          cur_byte++;
        }
        // go to the end of the next variable
        cur_byte -= 2 * var_size;
      }
    }
    // for big endian system (ez)
    else if (SYS_ENDIAN == BIG)
    {
      // get to the end of the first variable
      cur_byte += byte_cnt - 1;
      for (umax i = 0; i < byte_cnt; i++)
      {
        new_bit = (* cur_byte) >> (CHAR_BIT - 1);
        (* cur_byte) = (* cur_byte) << 1;
        (* cur_byte) += old_bit;
        old_bit = new_bit;
        cur_byte--;
      }
    }
  }
  // right bitshift
  // go throught the array from left to right
  else if (bitshift_type == BITSHIFT_RIGHT)
  {
    // for big endian system (sucks)
    if (SYS_ENDIAN == LITTLE)
    {
      // get to the start of the first variable
      cur_byte += var_size - 1;
      for (umax i = 0; i < arr_size; i++)
      {
        for (umax j = 0; j < var_size; j++)
        {
          new_bit = (* cur_byte) << (CHAR_BIT - 1);
          (* cur_byte) = (* cur_byte) >> 1;
          (* cur_byte) += old_bit;
          old_bit = new_bit;
          cur_byte--;
        }
        // advance to the start of the next variable (at the right)
        cur_byte += 2 * var_size;
      }
    }
    // for big endian system (ez)
    else if (SYS_ENDIAN == BIG)
    {
      // get to the start of the first variable
      // already there (cur_byte = array)
      for (umax i = 0; i < byte_cnt; i++)
      {
        new_bit = (* cur_byte) << (CHAR_BIT - 1);
        (* cur_byte) = (* cur_byte) >> 1;
        (* cur_byte) += old_bit;
        old_bit = new_bit;
        cur_byte++;
      }
    }
  }
  
  // bitshift done
  // return last bit moved
  return old_bit;
}


// arr_n_bitshift() function
// bitshift the bits of an array by shift_count
// this function just calls shift_count times the arr_bitshift() function
void arr_n_bitshift(void * array, umax arr_size, umax var_size, BITSHIFT_T bitshift_type, umax shift_count)
{
  while (shift_count-- > 0)
    arr_bitshift(array, arr_size, var_size, bitshift_type);
  return;
}


// arr_circ_bitshift() function
// bitshift the bits of an array keeping the bits that are going to
// get discarded. These bits are put (depending on the bitshift direction)
// on the end or at the start of the bitfield being bitshifted
byte arr_circ_bitshift(void * array, umax arr_size, umax var_size, BITSHIFT_T bitshift_type)
{
  // save the bit that will get lost on the bitshift
  // it will depend as well on the endian mode
  umax byte_cnt = arr_size * var_size; // number of bytes on the array
  byte * arr = array;
  byte princess_bit = arr_bitshift(array, arr_size, var_size, bitshift_type);
  
  // left bitshift
  if (bitshift_type == BITSHIFT_LEFT) {
    // go to the end of the last element of the array
    if (SYS_ENDIAN == LITTLE)
      arr[byte_cnt - var_size] += princess_bit;
    else if (SYS_ENDIAN == BIG)
      arr[byte_cnt - 1] += princess_bit;
  }
  // right bitshift
  else if (bitshift_type == BITSHIFT_RIGHT) {
    // go to the start of the first element of the array
    if (SYS_ENDIAN == LITTLE)
      arr[var_size - 1] += princess_bit;
    else if (SYS_ENDIAN == BIG)
      arr[0] += princess_bit;
  }
  
  // circular bitshift done
  // return bit saved
  return princess_bit;
}


// arr_n_circ_bitshift() function
// shifts the bits of an array in a circular way by shift_count
// this function just calls shift_count times the arr_circ_bitshift() function
void arr_n_circ_bitshift(void * array, umax arr_size, umax var_size, BITSHIFT_T bitshift_type, umax shift_count)
{
  while (shift_count-- > 0)
    arr_circ_bitshift(array, arr_size, var_size, bitshift_type);
  return;
}


// get_bits() function
// funtion to read bits from bytes in memory and send them to BIT_HOLDER for further processing
// It will return the numeric position of the first bit of the binary number in BIT_HOLDER. 
// Example:
// lpad_cnt = 4, bit_cnt = 5,
//          v    v      v 
// 00010100 |1010|1100 1|0101001 01010111
// 0x14      0xAC      0xA9      0x57
//           ^ src
smax get_bits(umax bit_cnt, umax lpad_cnt, void * src)
{    
  // check params
  if ((bit_cnt + lpad_cnt) > BIT_HOLDER_LENGTH || src == NULL)
    return -1;
  
  // clear bit and byte arrays
  clear_bit_holder();
  clear_byte_holder();
  
  // number of bytes the bits (bit_cnt + lshifts + lpad_cnt) will take in memory
  umax byte_cnt = bit_cnt + lpad_cnt;
  while ((byte_cnt % CHAR_BIT) != 0) byte_cnt++;
  byte_cnt /= CHAR_BIT;
  
  // copy the bytes that contain the bits into BYTE_HOLDER
  cp_mem_bytes(src, byte_cnt, BYTE_HOLDER + (BYTE_HOLDER_LENGTH - byte_cnt));
  
  // remove the left padding bits (array bitshift - non-circular)
  arr_n_bitshift(BYTE_HOLDER + (BYTE_HOLDER_LENGTH - byte_cnt),
                 byte_cnt, 1, BITSHIFT_LEFT, lpad_cnt);
  arr_n_bitshift(BYTE_HOLDER + (BYTE_HOLDER_LENGTH - byte_cnt),
                 byte_cnt, 1, BITSHIFT_RIGHT, (byte_cnt * CHAR_BIT) - bit_cnt);
  
  // number of bytes the bits (without left padding bits 1 and 2) will take
  byte_cnt = bit_cnt;
  while ((byte_cnt % CHAR_BIT) != 0) byte_cnt++;
  byte_cnt = byte_cnt / CHAR_BIT;
  
  // get the bits into BIT_HOLDER
  // start from the rightmost byte in BYTE_HOLDER
  // read each bit from right to left and write it to BIT_HOLDER
  for (umax i = 0; i < byte_cnt; i++)
    for (umax j = 0, byte = 1; j < CHAR_BIT; j++, byte <<= 1)
      BIT_HOLDER[BIT_HOLDER_LENGTH - (CHAR_BIT * i) - j - 1] = (byte & BYTE_HOLDER[BYTE_HOLDER_LENGTH - i - 1]) >> j;
  
  // return the starting position of the binary digits
  return BIT_HOLDER_LENGTH - bit_cnt;
}


// get_byte_bits() function
// function to get bits from memory but it operates with the byte bits first before
// getting the bits. This function exists because of little endian basically.
//
// The most crazy overcomplicated example of why this function exists:
//
// imagine number -pi (minus pi) stored in a 32 bits float in little endian and I want to get the bits
// of the exponent part of the float (8 bits), unfortunately, said float isn't byte aligned, it starts
// 3 bits left from were it is supposed to start
// so if the byte aligned float is like this:
// DB       0F       49       C0       (hex, in memory)
// 11011011 00001111 01001001 11000000 (binary, in memory)
// the 3 left bit aligned float is like this:
// whatever   61       E9       38       idk        (hex, in memory)
// |...|11011 01100001 11101001 00111000 000|.....| (binary, in memory)
// src        src + 1  src + 2  src + 3  src + 4
// to get the exponent bits I would have first to bitshift the bytes in which the float is in (5 bytes)
// to the left by 3 (to align it) then reverse the 5 bytes to get them in big endian order and then use
// the get_bits() function to get the desired bits from the process above.
// the get_byte_bits() works in 2 stages, the alignment and the bit extraction
//
// Alignment:
// a call of get_byte_bits() to align and get all the bits would be like
// get_byte_bits(5, 3, ..., ..., src, LITTLE);
// - 5 bytes
// - apply 3 left bitshifts to those 5 bytes
// - resulting variable is on little endian so reverse the order of the bytes after the bitshift
// In the reversal the 5 bytes in memory would look like:
// 00       C0       49       0F       DB
// 00000000 11000000 01001001 00001111 11011011
// ptr + 0  ptr + 1  ptr + 2  ptr + 3  ptr + 4
//
// Bit Extraction:
// If I want to apply the get_bits() function to get the exponent bits at this point I would do
// get_bits(8, 8 + 1, ptr) which the finalized call of get_byte_bits() would be at the end
// get_byte_bits(5, 3, 8, 9, src, LITTLE);
//
// I think I was high when doing this. The downside of this function is that to be able to
// define the parameters you have to be extremely sure of what you are requesting. You literally
// have to be able to see the bytes in memory like if they were in front of you. Also, you need
// to be aware of the bits per byte on your system (I try to build these functions to be able to
// be used on other bits per byte systems but as I cannot do tests on those I am not sure if the
// logic is complete)
//
// NOTE: a call with byte_cnt, equal to 0 will make the function to set
//       byte_cnt to be the ceil((bit_cnt + lpad_cnt) / CHAR_BIT)
//       usually I will use byte_cnt = 0 unless I am losing data on the 
//       bitshifts that I want
smax get_byte_bits(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{    
  // check params
  if (bit_cnt == 0 || src == NULL || endian == NO_ENDIAN)
    return -1;
  
  // check byte_cnt
  // and define it if it is 0
  if (byte_cnt == 0) {
    byte_cnt = lshifts + bit_cnt + lpad_cnt;
    while (byte_cnt % CHAR_BIT != 0) byte_cnt++;
    byte_cnt = byte_cnt / CHAR_BIT;
  }
  
  // first, copy the bytes requested into a temporal container
  byte * ptr = allocate_memory(byte_cnt, sizeof(byte));
  cp_mem_bytes(src, byte_cnt, ptr);
  // align the bits
  arr_n_bitshift(ptr, byte_cnt, sizeof(byte), BITSHIFT_LEFT, lshifts);
  
  // then, check the endian (if little then reverse the array)
  if (endian == LITTLE)
    reverse_array(ptr, byte_cnt, sizeof(byte));
  // now, on the operated bytes, apply the get_bits() function
  smax rtn = get_bits(bit_cnt, lpad_cnt, ptr);  
  // free memory
  free_memory(ptr);  
  return rtn;
}
